package com.samh.openrower2;


import android.app.Service;
import android.content.Intent;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.location.LocationListener;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.SystemClock;
import android.hardware.SensorManager;
import android.content.Context;
import android.sax.StartElementListener;
import android.util.Log;
import android.text.Html;


import com.samh.openrower2.DashboardActivity.StrokeRateReceiver;
import com.samh.openrower2.HalfSinoidDetector.Dir;

public class StrokeSensor implements SensorEventListener {
	
	private static final String TAG = "StrokeSensor";
	
	private SensorManager mSensorManager;
	private Sensor mAccelerometer;
	private Context mContext;
	private static float kFilteringFactor = 0.1f;
	private int mLastStrokeTimestamp = 0;
	private float filteredValues[] = new float[3];
	private long strokeCount = 0;
	private double strokeRate = 0;
	private String strokeRateString = "0";
	private boolean countStrokes = false;
	private final HalfSinoidDetector decelerationAmplitudeDetector = new HalfSinoidDetector(Dir.DOWN);
	private final HalfSinoidDetector accelerationAmplitudeDetector = new HalfSinoidDetector(Dir.UP);
	private Handler mHandler = new Handler();
	private float minAmp = 0;
	private float maxAmp = 0;
	private boolean catchStandby = false;
	
	public StrokeSensor(Context context) {
		// TODO Auto-generated constructor stub
		mContext = context;
		Log.d(TAG,"StrokeSensor Constructor");
	}
	
	public void startListener() {
		mSensorManager = (SensorManager) mContext.getSystemService(mContext.SENSOR_SERVICE);
		mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
		mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_NORMAL);
		detectNoStroke.run();
	}
	
	public void resetStrokeCont() {
		strokeCount = 0;
	}
	
	public void stopCountingStrokes() {
		countStrokes = false;
	}
	
	public void startCountingStrokes() {
		countStrokes = true;
	}

	@Override
	public void onAccuracyChanged(Sensor sensor, int accuracy) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void onSensorChanged(SensorEvent event) {
		// TODO Auto-generated method stub
		
		switch (event.sensor.getType()) {
		case Sensor.TYPE_ACCELEROMETER:
			
			long timestamp = SystemClock.elapsedRealtime();
			
			float values[] = filter(event.values);
			
			float filteredAmplitude = rootAcceleration(values);
			//Log.d(TAG, "Filtered Amp: "+filteredAmplitude);
			
			if(filteredAmplitude < minAmp) {
				minAmp = filteredAmplitude;
			}
			
			if(filteredAmplitude > maxAmp) {
				maxAmp = filteredAmplitude;
			}
			
			//Log.d(TAG, "Min Amp: "+minAmp+ " Max Amp: "+maxAmp);
			
			switch (accelerationAmplitudeDetector.add(filteredAmplitude)) {
			case VALID_EXIT:
				//Log.d(TAG, "Accel VALID_EXIT");
				onDropBelow(timestamp, accelerationAmplitudeDetector.maxVal);
				break;
			case TESHOLD_PASS:
				//Log.d(TAG, "Accel TESHOLD_PASS");
				onAccelerationTreshold(timestamp, filteredAmplitude);
				break;
			}

			
			switch (decelerationAmplitudeDetector.add(filteredAmplitude)) {
			case VALID_EXIT:
				//Log.d(TAG, "Decel VALID_EXIT");
				onRiseAbove(timestamp, decelerationAmplitudeDetector.maxVal);
				break;
			case TESHOLD_PASS:
				//Log.d(TAG, "Decel TESHOLD_PASS");
				onDecelerationTreshold(timestamp, filteredAmplitude);
				break;
			}
			
			
			
		}
		
	}
	
	private float rootAcceleration(float[] values) {
		
		 float sqrtAccel = (float) Math.sqrt(
				Math.pow(values[0], 2) +
				Math.pow(values[1], 2) +
				Math.pow(values[2], 2)
				);
		 
		 float accel = (float) sqrtAccel - SensorManager.STANDARD_GRAVITY;
		
		return accel;
	}
	
	protected void onDecelerationTreshold(long timestamp, float amplitude) {
		Log.d(TAG,"decel: "+amplitude);
		catchStandby = true;
	}

	protected void onAccelerationTreshold(long timestamp, float amplitude)  {
		Log.d(TAG,"accel: "+amplitude);
		if(catchStandby) {
			strokeDetected();
			catchStandby = false;
		}
	}
	
	protected void onDropBelow(long timestamp, float maxVal)  {
		//Log.d(TAG,"drop: "+maxVal);
		
	}
	
	protected void onRiseAbove(long timestamp, float maxVal) {
		//Log.d(TAG,"rise: "+maxVal);
		
	}
	
	private void strokeDetected() {
		
		int timestamp = (int) SystemClock.elapsedRealtime();
		Log.d(TAG, "STROKE");
		
		if(mLastStrokeTimestamp == 0) {
			mLastStrokeTimestamp = timestamp;
		}
	
		int elapsed = (timestamp - mLastStrokeTimestamp) == 0 ? 1 : (timestamp - mLastStrokeTimestamp);
		
		//strokeRate = (60 * 1000) / elapsed;
		double SPM = (double) 60000 / (double) elapsed;
		strokeRate = Math.round((SPM * 10) / 10);
		
		//TODO: need a preference for max, min stroke rate
		if(strokeRate < 70) {
			
			mLastStrokeTimestamp = timestamp;
			
			if(countStrokes) {
				strokeCount += 1;
			}
			
			broadcastStrokeRate();
			
		}
		
		
	}
	
	private void broadcastStrokeRate() {
		
		Intent i = new Intent(StrokeRateReceiver.STROKE_DETECTED);
		i.putExtra(StrokeRateReceiver.STROKE_RATE, strokeRate);
		i.putExtra(StrokeRateReceiver.STROKE_COUNT, strokeCount);
		i.putExtra(StrokeRateReceiver.STROKE_RATE_STRING, Integer.toString((int) strokeRate));
		mContext.sendBroadcast(i);
		
	}
	
	protected float[] filter(float[] values) {
		
		for (int i = 0; i < values.length; ++i) {
			
			float f = (values[i] * kFilteringFactor) + (filteredValues[i] * (1.0f - kFilteringFactor));
			
			if (!Float.isNaN(f)) {
				filteredValues[i] = f;
			}
		}
		
		return filteredValues;
	}
	
	Runnable detectNoStroke = new Runnable() {
		
		@Override
		public void run() {
			// TODO Auto-generated method stub
			//Log.d(TAG, "Running detectNoStroke: ");	
			if(mLastStrokeTimestamp + 7000 < SystemClock.elapsedRealtime() && strokeRate > 0) {
				strokeRate = 0;
				//Log.d(TAG, "No stroke detected.");	
				broadcastStrokeRate();
			}
			
			mHandler.postDelayed(this, 7000);
		}
	};

}
